//
//  raw_vector_impl.hpp
//  utilities
//
//  Created by Sam Jaffe on 11/9/14.
//  Copyright (c) 2014 Sam Jaffe. All rights reserved.
//

#pragma once

#include <stdexcept>
#include <cstring>

namespace math { namespace vector {
    
    template <size_t D, typename T>
    raw_vector<D, T>::raw_vector(std::initializer_list<T> vals) :
    detail::_raw_vector<D, T>(vals) {
        
    }
    
    template <size_t D, typename T>
    template <std::size_t D2, typename>
    raw_vector<D, T>::raw_vector(const raw_vector<D2, T>& that) :
    detail::_raw_vector<D, T>(that) {
        
    }
    
    template <typename T>
    raw_vector<3, T>::raw_vector(std::initializer_list<T> vals) :
    detail::_raw_vector<3, T>(vals) {
        
    }
    
    template <typename T>
    raw_vector<3, T>::raw_vector(const T& x, const T& y, const T& z) :
    detail::_raw_vector<3, T>{x, y, z} {
        
    }
    
    template <typename T>
    template <std::size_t D2, typename>
    raw_vector<3, T>::raw_vector(const raw_vector<D2, T>& that) :
    detail::_raw_vector<3, T>(that) {
        
    }
    
    template <typename T>
    raw_vector<4, T>::raw_vector(std::initializer_list<T> vals) :
    detail::_raw_vector<4, T>(vals) {
        
    }
    
    template <typename T>
    raw_vector<4, T>::raw_vector(const T& x, const T& y, const T& z, const T& w) :
    detail::_raw_vector<4, T>{x, y, z, w} {
        
    }
    
    template <typename T>
    template <std::size_t D2, typename>
    raw_vector<4, T>::raw_vector(const raw_vector<D2, T>& that) :
    detail::_raw_vector<4, T>(that) {
        
    }
    
    // [  i  j  k ]
    // [ t1 t2 t3 ] => i*t2*v3-i*t3*v2+j*t3*v1-j*t1*v3+k*t1*v2-k*t2*v1
    // [ v1 v2 v3 ]
    template <typename T>
    raw_vector<3, T> raw_vector<3, T>::cross(const raw_vector<3, T>& v) const {
        return raw_vector<3, T>{
            this->at(1)*v[2] - this->at(2)*v[1],
            this->at(2)*v[0] - this->at(0)*v[2],
            this->at(0)*v[1] - this->at(1)*v[0]};
    }
    
    template <typename T>
    raw_vector<2, T>::raw_vector(std::initializer_list<T> vals) :
    detail::_raw_vector<2, T>(vals) {
        
    }
    
    template <typename T>
    raw_vector<2, T>::raw_vector(const T& x, const T& y) :
    detail::_raw_vector<2, T>{x, y} {
        
    }
    
    template <typename T>
    template <std::size_t D2, typename>
    raw_vector<2, T>::raw_vector(const raw_vector<D2, T>& that) :
    detail::_raw_vector<2, T>(that) {
        
    }
    
    template <typename T>
    raw_vector<3, T> raw_vector<2, T>::cross(const raw_vector<2, T>& v) const {
        return raw_vector<3, T>(0, 0, (this->at(0)*v[1]) - (v[0]*this->at(1)));
    }
    
    template <size_t D, typename T>
    detail::_raw_vector<D, T>::_raw_vector(std::initializer_list<T> vals) {
        std::memcpy(data, vals.begin(), std::min(vals.size(), D));
    }
    
    template <size_t D, typename T>
    template <std::size_t D2, typename>
    detail::_raw_vector<D, T>::_raw_vector(const detail::_raw_vector<D2, T>& that) {
        std::memcpy(data, that.data, std::min(D2, D));
    }
    
    template <size_t D, typename T>
    T& detail::_raw_vector<D, T>::operator[] (size_t index) {
        return data[index];
    }
    
    template <size_t D, typename T>
    constexpr inline const T& detail::_raw_vector<D, T>::operator[] (size_t index) const noexcept {
        return data[index];
    }
    
    template <size_t D, typename T>
    T& detail::_raw_vector<D, T>::at(size_t index) {
        if (index < 0 || index > D) {
            throw std::out_of_range("");
        }
        return data[index];
    }
    
    template <size_t D, typename T>
    const T& detail::_raw_vector<D, T>::at(size_t index) const {
        if (index < 0 || index > D) {
            throw std::out_of_range("");
        }
        return data[index];
    }
    
    template <size_t D, typename T>
    raw_vector<D, T>& detail::_raw_vector<D, T>::operator+=(const raw_vector<D, T>& other) {
        for (std::size_t i = 0; i < D; ++i) {data[i] += other.data[i];}
        return static_cast<raw_vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    raw_vector<D, T>& detail::_raw_vector<D, T>::operator-=(const raw_vector<D, T>& other) {
        for (std::size_t i = 0; i < D; ++i) {data[i] -= other.data[i];}
        return static_cast<raw_vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    raw_vector<D, T>& detail::_raw_vector<D, T>::operator*=(const raw_vector<D, T>& other) {
        for (std::size_t i = 0; i < D; ++i) {data[i] *= other.data[i];}
        return static_cast<raw_vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    raw_vector<D, T>& detail::_raw_vector<D, T>::operator/=(const raw_vector<D, T>& other) {
        for (std::size_t i = 0; i < D; ++i) {data[i] /= other.data[i];}
        return static_cast<raw_vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    raw_vector<D, T>& detail::_raw_vector<D, T>::operator+=(const T& c) {
        for (std::size_t i = 0; i < D; ++i) {data[i] += c;}
        return static_cast<raw_vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    raw_vector<D, T>& detail::_raw_vector<D, T>::operator-=(const T& c) {
        for (std::size_t i = 0; i < D; ++i) {data[i] -= c;}
        return static_cast<raw_vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    raw_vector<D, T>& detail::_raw_vector<D, T>::operator*=(const T& c) {
        for (std::size_t i = 0; i < D; ++i) {data[i] *= c;}
        return static_cast<raw_vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    raw_vector<D, T>& detail::_raw_vector<D, T>::operator/=(const T& c) {
        for (std::size_t i = 0; i < D; ++i) {data[i] /= c;}
        return static_cast<raw_vector<D, T>&>(*this);
    }
    
    template <size_t D, typename T>
    raw_vector<D, T> detail::_raw_vector<D, T>::operator+(const raw_vector<D, T>& other) const {
        return raw_vector<D, T>(*this) += other;
    }
    
    template <size_t D, typename T>
    raw_vector<D, T> detail::_raw_vector<D, T>::operator-(const raw_vector<D, T>& other) const {
        return raw_vector<D, T>(*this) -= other;
    }
    
    template <size_t D, typename T>
    raw_vector<D, T> detail::_raw_vector<D, T>::operator*(const raw_vector<D, T>& other) const {
        return raw_vector<D, T>(*this) *= other;
    }
    
    template <size_t D, typename T>
    raw_vector<D, T> detail::_raw_vector<D, T>::operator/(const raw_vector<D, T>& other) const {
        return raw_vector<D, T>(*this) /= other;
    }
    
    template <size_t D, typename T>
    raw_vector<D, T> detail::_raw_vector<D, T>::operator+(const T& c) const {
        return raw_vector<D, T>(*this) += c;
    }
    
    template <size_t D, typename T>
    raw_vector<D, T> detail::_raw_vector<D, T>::operator-(const T& c) const {
        return raw_vector<D, T>(*this) -= c;
    }
    
    template <size_t D, typename T>
    raw_vector<D, T> detail::_raw_vector<D, T>::operator*(const T& c) const {
        return raw_vector<D, T>(*this) *= c;
    }
    
    template <size_t D, typename T>
    raw_vector<D, T> detail::_raw_vector<D, T>::operator/(const T& c) const {
        return raw_vector<D, T>(*this) /= c;
    }
    
    template <size_t D, typename T>
    raw_vector<D, T> detail::_raw_vector<D, T>::operator-() const {
        raw_vector<D, T> inv(*this);
        for (std::size_t i = 0; i < D; ++i) {inv[i] = -inv[i];}
        return inv;
    }
    
    template <size_t D, typename T>
    T detail::_raw_vector<D, T>::dot(const detail::_raw_vector<D, T>& v) const {
        T accum();
        for (std::size_t i = 0; i < D; ++i) {accum += data[i] * v[i];}
        return accum;
    }
    
    template <size_t D, typename T>
    T detail::_raw_vector<D, T>::lengthSquared() const {
        return this->dot(*this);
    }
    
    template <size_t D, typename T>
    auto detail::_raw_vector<D, T>::length() const -> decltype(std::sqrt(T())) {
        return std::sqrt(lengthSquared());
    }
    
    template <size_t D, typename T>
    T detail::_raw_vector<D, T>::distanceSquared(const raw_vector<D, T>& other) const {
        T accum{};
        for (std::size_t i = 0; i < D; ++i) {
            accum += (data[i] - other.data[i]) * (data[i] - other.data[i]);
        }
        return accum;
    }
    
    template <size_t D, typename T>
    auto detail::_raw_vector<D, T>::distance(const raw_vector<D, T>& other) const -> decltype(std::sqrt(T())) {
        return std::sqrt(distanceSquared(other));
    }
    
    template <size_t D, typename T>
    raw_vector<D, T> detail::_raw_vector<D, T>::unit() const {
        return *this/length();
    }
} }

